<!-- 富文本编辑器 插件地址：https://www.wangeditor.com/ -->
<template>
  <div class="editor-wrapper">
    <Toolbar
      class="editor-toolbar"
      :editor="editorRef"
      :mode="mode"
      :defaultConfig="toolbarConfig"
    />
    <Editor
      style="height: 500px; overflow-y: hidden"
      :mode="mode"
      :defaultConfig="editorConfig"
      @onCreated="onCreateEditor"
      @customPaste="customPaste"
    />
  </div>
</template>

<script setup lang="ts">
  import '@wangeditor/editor/dist/css/style.css'
  import { onBeforeUnmount, onMounted, ref, shallowRef, defineExpose } from 'vue'
  import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
  import { useUserStore } from '@/store/modules/user'
  import { ElMessage } from 'element-plus'
  import EmojiText from '@/utils/emojo'
  import { IDomEditor } from '@wangeditor/editor'
  import { ossSaveMinioFileUsingPost } from '@/api/4a/osswenjianshangchuan'

  // 编辑器实例
  const editorRef = shallowRef()
  let mode = ref('default')
  const userStore = useUserStore()
  // token
  let { accessToken } = userStore
  // 图片上传地址
  let server = `${import.meta.env.VITE_API_URL}/oss/saveMinioFile`

  const toolbarConfig = {
    // 重新配置工具栏，显示哪些菜单，以及菜单的排序、分组。
    // toolbarKeys: [],
    // 可以在当前 toolbarKeys 的基础上继续插入新菜单，如自定义扩展的菜单。
    // insertKeys: {
    //   index: 5, // 插入的位置，基于当前的 toolbarKeys
    //   keys: ['menu-key1', 'menu-key2']
    // }
    // 排除某些菜单
    excludeKeys: ['fontFamily'] //'group-video', 'fontSize', 'lineHeight'
  }

  const editorConfig = {
    placeholder: '请输入内容...',
    MENU_CONF: {
      // 上传图片
      uploadImage: {
        fieldName: 'file',
        maxFileSize: 50 * 1024 * 1024, // 大小限制
        maxNumberOfFiles: 10, // 最多可上传几个文件，默认为 100
        allowedFileTypes: ['image/*'], // 选择文件时的类型限制，默认为 ['image/*'] 。如不想限制，则设置为 []
        // 注意 ${import.meta.env.VITE_BASE_URL} 写你自己的后端服务地址
        server,
        // 传递token
        headers: { Authorization: accessToken },
        // 超时时间，默认为 10 秒
        timeout: 60 * 1000, // 5 秒
        // 单个文件上传成功之后
        onSuccess() {
          ElMessage.success(`图片上传成功 ${EmojiText[200]}`)
        },
        // 自定义插入图片
        customInsert(res: any, insertFn) {
          console.log(res, '122112')
          // TS 语法
          // customInsert(res, insertFn) {                  // JS 语法
          // res 即服务端的返回结果

          // 从 res 中找到 url alt href ，然后插入图片
          insertFn(res.data.path, res.data.fileName, res.data.path)
        },
        // 上传错误，或者触发 timeout 超时
        onError(file: File, err: any, res: any) {
          console.log(`上传出错`, err, res)
          ElMessage.error(`图片上传失败 ${EmojiText[500]}`)
        }
        // 注意：返回格式需要按照指定格式返回，才能显示图片
        // 上传成功的返回格式：
        //   "errno": 0, // 注意：值是数字，不能是字符串
        //   "data": {
        //       "url": "xxx", // 图片 src ，必须
        //       "alt": "yyy", // 图片描述文字，非必须
        //       "href": "zzz" // 图片的链接，非必须
        //   }
        // }
        // 上传失败的返回格式：
        // {
        //   "errno": 1, // 只要不等于 0 就行
        //   "message": "失败信息"
        // }
      },
      uploadVideo: {
        server,
        fieldName: 'file',
        headers: { Authorization: accessToken },
        // 单个文件的最大体积限制，默认为 10M
        maxFileSize: 100 * 1024 * 1024, // 100M
        //超时时间
        timeout: 60 * 1000,
        // 自定义插入视频
        customInsert(res: any, insertFn) {
          insertFn(res.data.path, res.data.poster)
        }
      }

      // 代码语言
      // codeLangs: [
      //   { text: 'CSS', value: 'css' },
      //   { text: 'HTML', value: 'html' },
      //   { text: 'XML', value: 'xml' },
      // ]
    }
  }

  const onCreateEditor = (editor: IDomEditor) => {
    editorRef.value = editor // 记录 editor 实例
    editor.on('fullScreen', () => {
      console.log('fullScreen')
    })
  }

  const setHtml = (html: string) => {
    editorRef.value.dangerouslyInsertHtml(html)
  }

  const getHtml = () => {
    return editorRef.value.getHtml()
  }

  const customPaste = async (editor: IDomEditor, event: ClipboardEvent, callback: Function) => {
    if (!event.clipboardData) return true

    const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
    const text = event.clipboardData
      .getData('text/plain')
      .replace(/@font-face{[^>]*div.Section0{page:Section0;}/g, '') // 获取粘贴的纯文本
    const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据

    // 如果只有纯文本，直接返回true使用默认粘贴行为
    if (!html && text) {
      return true
    }

    try {
      // 处理html内容
      if (html) {
        // 列表缩进会超出边框，直接过滤掉
        let processedHtml = html.replace(/text-indent:-(.*?)pt/gi, '')

        // 从html内容中查找图片
        const imgSrcs = findAllImgSrcsFromHtml(processedHtml)

        // 如果有图片且有rtf数据
        if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length && rtf) {
          const rtfImageData = extractImageDataFromRtf(rtf)

          if (rtfImageData.length) {
            // 将图片转换为base64
            processedHtml = replaceImagesFileSourceWithInlineRepresentation(
              processedHtml,
              imgSrcs,
              rtfImageData
            )

            // 上传图片到服务器
            for (let i = 0; i < rtfImageData.length; i++) {
              const base64Data = _convertHexToBase64(rtfImageData[i].hex)
              const blob = base64ToBlob(base64Data, rtfImageData[i].type)
                const file = new File(
                  [blob],
                  `paste_image_${i}.${rtfImageData[i].type.split('/')[1]}`,
                  {
                    type: rtfImageData[i].type
                  }
                )

              try {
                const res = await ossSaveMinioFileUsingPost({
                  body: {},
                  file,
                  options: {
                    headers: {
                      Authorization: accessToken
                    }
                  }
                })

                if (res.code === 0) {
                  // 替换base64为服务器返回的url
                  processedHtml = processedHtml.replace(
                    `data:${rtfImageData[i].type};base64,${base64Data}`,
                    res.data.path
                  )
                }
              } catch (err) {
                console.error('图片上传失败:', err)
                ElMessage.error(`图片上传失败 ${EmojiText[500]}`)
              }
            }

            editor.dangerouslyInsertHtml(processedHtml)
            event.preventDefault()
            return false
          }
        }

        // 如果html中没有图片，直接插入处理后的html
        editor.dangerouslyInsertHtml(processedHtml)
        event.preventDefault()
        return false
      }

      return true
    } catch (err) {
      console.error('粘贴处理出错:', err)
      return true
    }
  }

  /**
   * 从html代码中匹配返回图片标签img的属性src的值的集合
   * @param htmlData
   * @return Array
   */
  const findAllImgSrcsFromHtml = (htmlData: string) => {
    const imgReg = /<img.*?(?:>|\/>)/gi
    const srcReg = /src=["']?([^"']*?)["']?(?:\s|>|\/>)/i

    const arr = htmlData.match(imgReg)
    if (!arr || !arr.length) {
      return false
    }

    const srcArr: string[] = []
    for (const img of arr) {
      const src = img.match(srcReg)
      if (src && src[1]) {
        srcArr.push(src[1])
      }
    }

    return srcArr
  }

  /**
   * 从rtf内容中匹配返回图片数据的集合
   * @param rtfData
   * @return Array
   */
  const extractImageDataFromRtf = (rtfData: string) => {
    if (!rtfData) {
      return []
    }

    const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
    const regexPicture = new RegExp(
      '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}',
      'g'
    )
    const images = rtfData.match(regexPicture)
    const result = []

    if (images) {
      for (const image of images) {
        let imageType: string | null = null

        if (image.includes('\\pngblip')) {
          imageType = 'image/png'
        } else if (image.includes('\\jpegblip')) {
          imageType = 'image/jpeg'
        }

        if (imageType) {
          result.push({
            hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
            type: imageType
          })
        }
      }
    }

    return result
  }

  /**
   * 将html内容中img标签的属性值替换
   * @param htmlData html内容
   * @param imageSrcs html中img的属性src的值的集合
   * @param imagesHexSources rtf中图片数据的集合，与html内容中的img标签对应
   * @param isBase64Data 是否是Base64的图片数据
   * @return String
   */
  const replaceImagesFileSourceWithInlineRepresentation = (
    htmlData: string,
    imageSrcs: string[],
    imagesHexSources: any[],
    isBase64Data = true
  ) => {
    if (imageSrcs.length === imagesHexSources.length) {
      for (let i = 0; i < imageSrcs.length; i++) {
        const newSrc = isBase64Data
          ? `data:${
              imagesHexSources[i].type
            };base64,${_convertHexToBase64(imagesHexSources[i].hex)}`
          : imagesHexSources[i]

        htmlData = htmlData.replace(imageSrcs[i], newSrc)
      }
    }

    return htmlData
  }

  /**
   * 十六进制转base64
   */
  const _convertHexToBase64 = (hexString: string) => {
    return btoa(
      hexString
        .match(/\w{2}/g)
        .map((char) => {
          return String.fromCharCode(parseInt(char, 16))
        })
        .join('')
    )
  }

  // 新增 base64 转 Blob 方法
  const base64ToBlob = (base64: string, type: string): Blob => {
    const byteCharacters = atob(base64)
    const byteArrays = []

    for (let offset = 0; offset < byteCharacters.length; offset += 512) {
      const slice = byteCharacters.slice(offset, offset + 512)
      const byteNumbers = new Array(slice.length)

      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i)
      }

      const byteArray = new Uint8Array(byteNumbers)
      byteArrays.push(byteArray)
    }

    return new Blob(byteArrays, { type })
  }

  onMounted(() => {
    overrideIcon()
  })

  //  替换默认图标
  const overrideIcon = () => {
    setTimeout(() => {
      const icon0 = document.querySelector(`button[data-menu-key="bold"]`)
      if (icon0) icon0.innerHTML = "<i class='iconfont-sys'>&#xe630;</i>"

      const icon1 = document.querySelector(`button[data-menu-key="blockquote"]`)
      if (icon1) icon1.innerHTML = "<i class='iconfont-sys'>&#xe61c;</i>"

      const icon2 = document.querySelector(`button[data-menu-key="underline"]`)
      if (icon2) icon2.innerHTML = "<i class='iconfont-sys'>&#xe65a;</i>"

      const icon3 = document.querySelector(`button[data-menu-key="italic"]`)
      if (icon3) icon3.innerHTML = "<i class='iconfont-sys'>&#xe638;</i>"

      const icon4 = document.querySelector(`button[data-menu-key="group-more-style"]`)
      if (icon4) icon4.innerHTML = "<i class='iconfont-sys'>&#xe648;</i>"

      const icon5 = document.querySelector(`button[data-menu-key="color"]`)
      if (icon5) icon5.innerHTML = "<i class='iconfont-sys'>&#xe68c;</i>"

      const icon6 = document.querySelector(`button[data-menu-key="bgColor"]`)
      if (icon6) icon6.innerHTML = "<i class='iconfont-sys'>&#xe691;</i>"

      const icon7 = document.querySelector(`button[data-menu-key="bulletedList"]`)
      if (icon7) icon7.innerHTML = "<i class='iconfont-sys'>&#xe64e;</i>"

      const icon8 = document.querySelector(`button[data-menu-key="numberedList"]`)
      if (icon8) icon8.innerHTML = "<i class='iconfont-sys'>&#xe66c;</i>"

      const icon9 = document.querySelector(`button[data-menu-key="todo"]`)
      if (icon9) icon9.innerHTML = "<i class='iconfont-sys'>&#xe641;</i>"

      const icon10 = document.querySelector(`button[data-menu-key="group-justify"]`)
      if (icon10) icon10.innerHTML = "<i class='iconfont-sys'>&#xe67e;</i>"

      const icon11 = document.querySelector(`button[data-menu-key="group-indent"]`)
      if (icon11) icon11.innerHTML = "<i class='iconfont-sys'>&#xe63e;</i>"

      const icon12 = document.querySelector(`button[data-menu-key="emotion"]`)
      if (icon12) icon12.innerHTML = "<i class='iconfont-sys'>&#xe690;</i>"

      const icon13 = document.querySelector(`button[data-menu-key="insertLink"]`)
      if (icon13) icon13.innerHTML = "<i class='iconfont-sys'>&#xe63a;</i>"

      const icon14 = document.querySelector(`button[data-menu-key="group-image"]`)
      if (icon14) icon14.innerHTML = "<i class='iconfont-sys'>&#xe634;</i>"

      const icon15 = document.querySelector(`button[data-menu-key="insertTable"]`)
      if (icon15) icon15.innerHTML = "<i class='iconfont-sys'>&#xe67b;</i>"

      const icon16 = document.querySelector(`button[data-menu-key="codeBlock"]`)
      if (icon16) icon16.innerHTML = "<i class='iconfont-sys'>&#xe68b;</i>"

      const icon17 = document.querySelector(`button[data-menu-key="divider"]`)
      if (icon17) icon17.innerHTML = "<i class='iconfont-sys'>&#xe66d;</i>"

      const icon18 = document.querySelector(`button[data-menu-key="undo"]`)
      if (icon18) icon18.innerHTML = "<i class='iconfont-sys'>&#xe65e;</i>"

      const icon19 = document.querySelector(`button[data-menu-key="redo"]`)
      if (icon19) icon19.innerHTML = "<i class='iconfont-sys'>&#xe659;</i>"

      const icon20 = document.querySelector(`button[data-menu-key="fullScreen"]`)
      if (icon20) icon20.innerHTML = "<i class='iconfont-sys'>&#xe633;</i>"

      const icon21 = document.querySelector(`button[data-menu-key="tableFullWidth"]`)
      if (icon21) icon21.innerHTML = "<i class='iconfont-sys'>&#xe67b;</i>"
    }, 10)
  }

  // 获取工具栏
  // const getToolbar = (editor: IDomEditor) => {
  //   setTimeout(() => {
  //     const toolbar = DomEditor.getToolbar(editor)
  //     console.log(toolbar?.getConfig().toolbarKeys) // 当前菜单排序和分组
  //   }, 300)
  // }

  // 组件销毁时，也及时销毁编辑器
  onBeforeUnmount(() => {
    const editor = editorRef.value
    if (editor == null) return
    editor.destroy()
  })

  defineExpose({
    setHtml,
    getHtml
  })
</script>

<style lang="scss">
  /* 编辑器容器 */
  .editor-wrapper {
    width: 100%;
    height: 100%;
    overflow: hidden;
    border: 1px solid rgba(var(--art-gray-300-rgb), 0.8);
    border-radius: calc(var(--custom-radius) / 2 + 2px) !important;

    .iconfont-sys {
      font-size: 20px !important;
    }

    .menu-item {
      display: flex;
      flex-direction: row;
      align-items: center;

      i {
        margin-right: 5px;
      }
    }

    /* 工具栏 */
    .editor-toolbar {
      border-bottom: 1px solid var(--art-border-color);
    }

    /* 下拉选择框配置 */
    .w-e-select-list {
      min-width: 140px;
      padding: 5px 10px 10px;
      border: none;
      border-radius: 12px;
    }

    /* 下拉选择框元素配置 */
    .w-e-select-list ul li {
      margin-top: 5px;
      font-size: 15px !important;
      border-radius: 10px;
    }

    /* 下拉选择框 正文文字大小调整 */
    .w-e-select-list ul li:last-of-type {
      font-size: 16px !important;
    }

    /* 下拉选择框 hover 样式调整 */
    .w-e-select-list ul li:hover {
      background-color: var(--art-gray-200);
    }

    :root {
      /* 激活颜色 */
      --w-e-toolbar-active-bg-color: var(--art-gray-200);

      /* toolbar 图标和文字颜色 */
      --w-e-toolbar-color: #000;

      /* 表格选中时候的边框颜色 */
      --w-e-textarea-selected-border-color: #ddd;

      /* 表格头背景颜色 */
      --w-e-textarea-slight-bg-color: var(--art-gray-200);
    }

    /* 工具栏按钮样式 */
    .w-e-bar-item button {
      border-radius: 8px;
    }

    /* 工具栏 hover 按钮背景颜色 */
    .w-e-bar-item button:hover {
      background-color: var(--art-gray-200);
    }

    /* 工具栏分割线 */
    .w-e-bar-divider {
      height: 20px;
      margin-top: 10px;
      background-color: #ccc;
    }

    /* 工具栏菜单 */
    .w-e-bar-item-group .w-e-bar-item-menus-container {
      min-width: 120px;
      padding: 10px 0;
      border: none;
      border-radius: 12px;
    }

    /* 代码块 */
    .w-e-text-container [data-slate-editor] pre > code {
      padding: 0.6rem 1rem;
      background-color: var(--art-gray-100);
      border-radius: 6px;
    }

    /* 弹出框 */
    .w-e-drop-panel {
      border: 0;
      border-radius: 12px;
    }

    a {
      color: #318ef4;
    }

    /* 表格样式优化 */
    .w-e-text-container [data-slate-editor] .table-container th {
      border-right: none;
    }

    .w-e-text-container [data-slate-editor] .table-container th:last-of-type {
      border-right: 1px solid #ccc !important;
    }

    /* 引用 */
    .w-e-text-container [data-slate-editor] blockquote {
      background-color: rgba(var(--art-gray-300-rgb), 0.25);
      border-left: 4px solid var(--art-gray-300);
    }

    /* 输入区域弹出 bar  */
    .w-e-hover-bar {
      border-radius: 10px;
    }

    /* 超链接弹窗 */
    .w-e-modal {
      border: none;
      border-radius: 12px;
    }

    /* 图片样式调整 */
    .w-e-text-container [data-slate-editor] .w-e-selected-image-container {
      overflow: inherit;

      &:hover {
        border: 0;
      }

      img {
        border: 1px solid transparent;
        transition: border 0.3s;

        &:hover {
          border: 1px solid #318ef4 !important;
        }
      }

      .w-e-image-dragger {
        width: 12px;
        height: 12px;
        background-color: #318ef4;
        border: 2px solid #fff;
        border-radius: 12px;
      }

      .left-top {
        top: -6px;
        left: -6px;
      }

      .right-top {
        top: -6px;
        right: -6px;
      }

      .left-bottom {
        bottom: -6px;
        left: -6px;
      }

      .right-bottom {
        right: -6px;
        bottom: -6px;
      }
    }
  }
</style>
